#include "stdafx.h"
#include "game_graph.hpp"
#include <fstream>
#include <pantheios/inserters/integer.hpp>
#include <pantheios/inserters/real.hpp>
#include <boost/bind.hpp>
#include <boost/any.hpp>
#include <iostream>
#include <iomanip>		// for setw
#include "actor_manager.hpp"
#include <iostream>
#include <algorithm>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include "tile_image.hpp"
#include "game_map_mover.hpp"
#include <algorithm>
using namespace boost ;
namespace FndGame2D {

  struct point_in_buddy : std::unary_function<game_map_mover*, bool> {
    bool operator()(const game_map_mover* m_f) const {
      return m_f->bounding_box().Contains(point.x, point.y) ;
    }
    point_in_buddy(const mover::position_type& f) : point(f) {}
    const mover::position_type& point ;
  } ;
  struct buddy_finder : std::unary_function<game_map_mover*, bool> {
    bool operator()(const game_map_mover* f) const {
      return m_f->name() == f->name() ;
    }
    buddy_finder(const game_map_mover* f) : m_f(f) {}
    const game_map_mover* m_f ;

  } ;
  //class record_fathers : defalut_bfs
  struct test_bfs_visitor : public default_dijkstra_visitor {
    template<class Vertex, class Graph>
    void finish_vertex(Vertex v, Graph& g)
    {
      pantheios::log_INFORMATIONAL("finish: ", pantheios::integer(v), 
				   "\t",
				   pantheios::real(m_dist[v]),"\n") ;
      if(m_long_dist >= m_dist[v]) {
	m_init.push_back(v) ;
      } else {
	// i could not find a good way to stop the search.
	throw true ;
      }
    }
    test_bfs_visitor(float  max_dist, 
		     std::vector<size_t>& init,
		     const std::vector<float>& dist) : m_long_dist(max_dist),
						       m_init(init),
						       m_dist(dist) {
    }
    float m_long_dist ;
    std::vector<size_t>& m_init ;
    const std::vector<float>& m_dist ;
  } ;

  const std::string game_graph::LOAD_MAP_FROM_FILE("load_map_from_file") ;
  const std::string game_graph::SET_TILE_SIZE("set_tile_size") ;
  const std::string game_graph::SET_TILE_IMAGE("set_tile_image") ;
  const std::string game_graph::SET_BACK_IMAGE("set_back_image") ;
  const std::string game_graph::FIX_ROAD("fix_road") ;
  game_graph::game_graph(const std::string& name, int draw_level,
			 const mover::position_type& pos,
			 float angle) : mover(name, draw_level,pos, angle),
					m_graph(0),
					m_width(0),
					m_height(0),
					m_tile_size(0),
					m_back_tile(0),
					m_draw_back_sprite(new sf::Sprite),
					m_tile_image(0),
					m_is_need_fix_road(false),
					m_current_valid_buddy(0) {
    message_handler.add_handle(LOAD_MAP_FROM_FILE, 
			       boost::bind(&game_graph::handle_load_map_from_file_message,
					   this, _1)) ;
    message_handler.add_handle(SET_TILE_SIZE,
			       boost::bind(&game_graph::handle_set_tile_size_message,
					   this, _1)) ;
    message_handler.add_handle(SET_BACK_IMAGE,
			       boost::bind(&game_graph::handle_set_back_tile_image_message,
					   this, _1)) ;
    message_handler.add_handle(SET_TILE_IMAGE,
			       boost::bind(&game_graph::handle_set_tile_image_message,
					   this, _1)) ;

    renderer = boost::bind(&game_graph::test_render, this, _1) ;
    m_circle = sf::Shape::Circle(0.0f, 0.0f, 3.0f, sf::Color(255,0,0)) ;

    updater2.connect(sigc::mem_fun(this, &game_graph::update_game_graph)) ;
   
  }
  bool game_graph::handle_set_tile_image_message(const normal_thinker_message_type& msg) {
    try {
      m_tile_image = any_cast<tile_image*>(msg.param) ;
      return true ;
    } catch(bad_any_cast& e) {
      pantheios::log_WARNING("ex handle_set_tile_image_message: " , e, "\n") ;
      return false ;
    }

  }
  bool game_graph::handle_set_back_tile_image_message(const normal_thinker_message_type& msg) {
    try {

      m_back_tile = any_cast<sf::Image*>(msg.param) ;
      m_draw_back_sprite->SetImage(*m_back_tile) ;
      return true ;
    } catch(bad_any_cast& e) {
      pantheios::log_WARNING("ex handle_set_back_tile_image_message:" , e , "\n") ;
      return false ;
    }
  }
  void game_graph::update_game_graph(float dummy) {
    if(m_is_need_fix_road) return ;
    thinker_manager* mgr = get_property<thinker_manager*>(thinker_manager::HAS_MANAGER, (thinker_manager*)0) ;
    if(!mgr) return ;
    const sf::Input& input = mgr->system_window().GetInput() ;
    
    if(input.IsMouseButtonDown(sf::Mouse::Right)) {
      m_is_holding = true ;
      int x = input.GetMouseX() ;
      int y = input.GetMouseY() ;
      const sf::View* render_view = mgr->current_render_view() ;
      const sf::Vector2f& center = render_view->GetCenter() ;
      const sf::Vector2f& half_size = render_view->GetHalfSize() ;
      x += static_cast<int>(center.x - half_size.x) ;
      y += static_cast<int>(center.y - half_size.y) ;
      if(x < m_tile_size * m_width && y < m_tile_size * m_height) {
	m_current_valid_buddy = find_valid_buddy() ;
	if(!m_current_valid_buddy) {
	  pantheios::log_DEBUG("could not find the buddy\n") ;
	  return ;
	} else {
	  pantheios::log_DEBUG("find the buddy : " , 
			       m_current_valid_buddy->name(), 
			       "buddy step is: ", 
			       pantheios::integer(m_current_valid_buddy->step())) ;
      }
      sf::Clock clock ;
      typedef graph_traits<game_graph_t>::vertex_iterator VertexIter ;
      VertexIter ei, ei_end ;

      x /= m_tile_size ;
      y /= m_tile_size ;
      if(m_current_x != x || m_current_y != y) {
	m_current_y = y ;
	m_current_x = x ;
	typedef graph_traits<game_graph_t>::vertex_descriptor Vertex ;
	Vertex v = vertex(x + y * m_width, *m_graph) ;
	m_valid.clear() ;
	try {
	  dijkstra_shortest_paths(*m_graph, v, weight_map(get(edge_weight,*m_graph)).
				  distance_map(&m_distances[0]).
				  predecessor_map(&m_processors[0]).
				  visitor(test_bfs_visitor(static_cast<float>(m_current_valid_buddy->step()), m_valid,m_distances ))) ;
	} catch (bool& /*e */) {
	  pantheios::log_DEBUG("find the obj\n") ;
	}
	std::cout << "caclute w: " << v << std::endl ;
      }
      //	pantheios::log_ERROR("time last: " , pantheios::real(clock.GetElapsedTime()), "\n") ;
    }
  }
}

  bool game_graph::handle_load_map_from_file_message(const normal_thinker_message_type& msg) {
    return create_graph_from_file(
				  boost::any_cast<std::string>(msg.param).c_str()) ;
  }
  bool game_graph::create_graph_from_file(const char* file_name) {

    std::fstream fin(file_name) ;
    if(!fin) return false ;

    fin >> m_width >> m_height >> m_tile_size ;
    if(m_width < 0 || m_height < 0) {
      m_width = m_height = 0 ;
      fin.close() ;
      return false ;
    }
    try {
      int n = 0 ;
      fin >> n ;
      if(m_graph) delete m_graph ;
      m_graph = new game_graph_t(m_width * m_height) ;
      m_processors.resize(m_width * m_height) ;
      m_distances.resize(m_width * m_height) ;
      property_map<game_graph_t, vertex_weight_t>::type weight = get(vertex_weight, *m_graph) ;
      property_map<game_graph_t, vertex_pic_t>::type pic = get(vertex_pic, *m_graph) ;
      for(size_t i = 0 ; i < num_vertices(*m_graph) ; ++i) weight[i] = 1 ;
      for(int i =0 ; i < n ; ++i) {
	graph_traits<game_graph_t>::vertex_descriptor id ;
	std::string pic_name ;
	float w ;
	fin >> id >> pic_name >>w ;
	weight[id] = w ;
	pic[id] = pic_name ;
      }
    } catch(boost::bad_any_cast& e) {
      pantheios::log_ERROR("ex when load_map_from_file: " , e, "\n") ;
      return 1 ;
    }
    fin.close() ;
    init_map() ;
    return true ;
  }

  mover::position_type game_graph::get_map_grid_position(size_t pos) {
    size_t w = pos % m_width ;
    size_t h = pos / m_width ;
    if(h >= m_height) {
      pantheios::log_ERROR(pantheios::integer(pos), " is not a valid game map pos.\n") ;
      throw std::range_error("out of range\n") ;
    }

    return mover::position_type(x() + w * m_tile_size, y() + h * m_tile_size) ;
  }
  void game_graph::test_render(sf::RenderTarget& target) {
    using namespace boost ;
    int width = m_width ;
    int height = m_height ;
    if(width * height == 0 || !m_graph) return ;

    for(int i = 0 ; i <= width ; ++i) {
      
      sf::Shape line =  sf::Shape::Line(static_cast<float>(i * m_tile_size), 
					0.f, static_cast<float>(i * m_tile_size), 
					static_cast<float>(height * m_tile_size), 
					1.5f, sf::Color(0,255,0)) ;
      target.Draw(line) ;
      
    }
    for(int j = 0 ; j <= height ; ++j) {
      sf::Shape line = sf::Shape::Line(0.f, j * m_tile_size, width * m_tile_size,
				       j * m_tile_size,
				       1.5f, sf::Color(255,0,0)) ;
      target.Draw(line) ;
    }

    property_map<game_graph_t, vertex_weight_t>::type weight = get(vertex_weight, *m_graph) ;

    typedef graph_traits<game_graph_t>::vertex_iterator VertexIter ;
    VertexIter ei, ei_end ;
    property_map<game_graph_t, vertex_pic_t>::type pic = get(vertex_pic, *m_graph) ;
    for(tie(ei,ei_end) = vertices(*m_graph) ; ei != ei_end ; ++ei) {
      int x, y;
      x = *ei % width ;
      y = *ei / width ;
      float w = weight[*ei] ; 
      const std::string& t_n = pic[*ei] ;
      if(!t_n.empty()) {
	const sf::IntRect& r = m_tile_image->get_rect(t_n) ;
	m_draw_back_sprite->SetSubRect(r) ;
	m_draw_back_sprite->SetPosition(x*m_tile_size, y*m_tile_size) ;
	target.Draw(*m_draw_back_sprite) ;	
      }
    }
    if(!m_valid.empty()) {
      draw_can_go_region(target) ;
    }
    thinker_manager* mgr = get_property<thinker_manager*>(thinker_manager::HAS_MANAGER, (thinker_manager*)0) ;
    if(!mgr) return ;
    const sf::Input& input = mgr->system_window().GetInput() ;
    if(input.IsMouseButtonDown(sf::Mouse::Left)) {
      sf::Vector2f mouse_pos_in_view = mgr->get_mouse_pos_in_render_view() ;
      int x = static_cast<int>(mouse_pos_in_view.x)/m_tile_size ;
      int y = static_cast<int>(mouse_pos_in_view.y)/m_tile_size ;
      size_t index = x + y * m_width ;
    if(!m_is_need_fix_road && m_current_valid_buddy) {
      mgr->handle_message(make_message(name(), m_current_valid_buddy->name(), 0.0f, m_current_valid_buddy->SET_MOVE_INIT_POS,
index)) ;
    }

      for(; m_processors[index] != index ; index = m_processors[index]) {
	x = index%m_width ;
	y = index/m_width ;
	const sf::IntRect& r = m_tile_image->get_rect("road") ;
	m_draw_back_sprite->SetSubRect(r) ;
	m_draw_back_sprite->SetPosition(x * m_tile_size, y * m_tile_size) ;
	target.Draw(*m_draw_back_sprite) ;
      } 
    }
  }

  void game_graph::remove_buddy(game_map_mover* buddy) {
    m_buddies.erase(std::find_if(m_buddies.begin(), m_buddies.end(), buddy_finder(buddy))) ;
  }
  game_map_mover* game_graph::current_valid_buddy() {
    if(!m_is_need_fix_road)
      m_current_valid_buddy = find_valid_buddy() ;
    return m_current_valid_buddy ;
  }
  
  game_map_mover* game_graph::find_valid_buddy() {
    thinker_manager* mgr = get_property<thinker_manager*>(thinker_manager::HAS_MANAGER, (thinker_manager*)0) ;
    if(!mgr) return 0;
    const sf::Input& input = mgr->system_window().GetInput() ;
    sf::Vector2f point = mgr->get_mouse_pos_in_render_view() ;
    buddy_map_type::iterator it(std::find_if(m_buddies.begin(), m_buddies.end(), point_in_buddy(point))) ;
    if(it == m_buddies.end()) return 0 ;
    return *it ;
  }

  void game_graph::draw_can_go_region(sf::RenderTarget& target) {
    pos_vec::iterator it_begin(m_valid.begin()), it_end(m_valid.end()) ;
    for( ; it_begin != it_end ; ++it_begin) {
      int xx = *it_begin % m_width ;
      int yy = *it_begin / m_height ;
      sf::Shape rect = sf::Shape::Rectangle(x() + xx * m_tile_size,
					    y() + yy * m_tile_size,
					    x() + (xx + 1) * m_tile_size,
					    y() + (yy + 1) * m_tile_size,
					    sf::Color(0, 255, 0, 128)) ;
      target.Draw(rect) ;
    }
  }

  bool game_graph::handle_set_tile_size_message(const normal_thinker_message_type& msg) {
    try {
      m_tile_size = any_cast<int>(msg.param) ;
      return true ;
    } catch(const bad_any_cast&) {
      return false;
    }
  }

  bool game_graph::handle_fix_road_message(const normal_thinker_message_type& msg) {
    try {
      m_is_need_fix_road = boost::any_cast<bool>(msg.param) ;
      return true ;
    } catch(boost::bad_any_cast& /*e*/) {
      pantheios::log_DEBUG("ex", msg.sender, " ", msg.receiver, " ", msg.msg, "\n") ;
      return false ;
    }
  }


  void game_graph::init_map() {

    if(m_width == 0 || m_height == 0 || m_width * m_height == 1)
      return ;
    property_map<game_graph_t, vertex_weight_t>::type  vweight = get(vertex_weight, *m_graph) ; 
    for(int i = 0 ; i < m_width -1 ; ++i)
      for(int j = 0 ; j < m_height -1 ; ++j) {
	int u = j * m_width + i ;
	int v = j * m_width + i + 1 ;
	float w =std:: max(vweight(u),vweight(v));
	property<edge_weight_t, float> ep(w) ;
	add_edge(j * m_width + i , j * m_width + i +1,ep, *m_graph) ;
	v = (j+1) * m_width + i ;
	w = std::max(vweight(v), vweight(u)) ;
	add_edge(j * m_width + i , ( j + 1 ) * m_width + i, 
		 property<edge_weight_t,float>(w),
		 *m_graph) ;
      }
    for(int j = 0 ; j < m_height -1 ; j++) {
      int u = j * m_width + m_width -1 ;
      int v = (j+1) * m_width + m_width - 1 ;
      float w =std::max(vweight(u),vweight(v));
      property<edge_weight_t, float> ep(w) ;
      add_edge(u, v ,ep, *m_graph) ;
    }
    for(int i = 0 ; i < m_width -1 ; i++) {
      int u = (m_height-1) * m_width + i ;
      int v = (m_height-1) * m_width + i + 1 ;
      float w = std::max(vweight(u),vweight(v));
      property<edge_weight_t, float> ep(w) ;
      add_edge(u,v,ep, *m_graph) ; 
    }
  }
  void game_graph::test_self_connection(float dummy, unsigned long id) {
    static int i = 0 ;
    pantheios::log_DEBUG("hello world test_self_connection\n") ;
    i++ ;
    if(i > 10) remove_connection(id) ;
  }
}
